﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MS.InformationManagement.DAL.DocumentCenter;
using Microsoft.SharePoint;
using MS.InformationManagement.DAL.DTOs;
using MS.InformationManagement.DocumentIDServiceApp;
using MS.InformationManagement.Support.Constants;
using MS.InformationManagement.Core.RecordSiteQuery;
using MS.InformationManagement.DAL.RecordCenter;
using Microsoft.SharePoint.Taxonomy;

namespace MS.InformationManagement.BLL.DocIdGeneration
{
    public class DocIdStructureBLL
    {
        private static int GetNextID(string bankName)
        {
            int nextID = 0;
            DocumentIDServiceClient client = new DocumentIDServiceClient(SPServiceContext.Current);
            nextID = client.GetNextDocumentID(bankName, ExecuteOptions.None);
            return nextID;
        }

        private static int GetNextID(string bankName, SPSite site)
        {
            int nextID = 0;
            DocumentIDServiceClient client = new DocumentIDServiceClient(SPServiceContext.GetContext(site));
            nextID = client.GetNextDocumentID(bankName, ExecuteOptions.None);
            return nextID;
        }

        private static string GetRadTypeNumberStructure(string radTypeTitle, out IEnumerable<ElementoDeConfiguracionDeEstructuraDeRadicado> fields, DCDocumentCenterDataContext context)
        {
            StringBuilder structure = new StringBuilder();

            TipoDeRadicado radType = null;

            radType = context.TiposDeRadicados.SingleOrDefault(rad => rad.Title.Equals(radTypeTitle));

            if (radType == default(TipoDeRadicado)) { throw new Exception(string.Format("la estuctura de generación de el tipo de radicado {0} no se encuentra configurado en el sistema", radTypeTitle)); }

            fields = radType.ElementoDeConfiguracionDeEstructuraDeRadicado;

            foreach (ElementoDeConfiguracionDeEstructuraDeRadicado config in radType.ElementoDeConfiguracionDeEstructuraDeRadicadoOrdenados) { structure.Append(config.Campo.Etiqueta); }

            return structure.ToString();
        }

        public static string GetDocumentID(SPListItem document)
        {

            string documentNumber = string.Empty;
            switch (document.ContentType.Name)
            {
                case GlobalConstants.CTTN_RADICADO_EMPRESA:
                case GlobalConstants.CTTN_RADICADO_EMPRESA_SALIDA:
                case GlobalConstants.CTTN_RADICADO_INTERNO:
                case GlobalConstants.CTTN_RADICADO_PERSONA:
                case GlobalConstants.CTTN_RADICADO_PERSONA_SALIDA:
                    using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(document.Web.Url))
                    {
                        string docType = (document[CustomSiteColumns.FIELD_DOCSET_KIND] as TaxonomyFieldValue).Label.ToUpper();
                        IEnumerable<ElementoDeConfiguracionDeEstructuraDeRadicado> fields = null;
                        documentNumber = GetDocumentID(document, GetRadTypeNumberStructure(docType, out fields, ctx), docType, fields, ctx);
                    }
                    break;
                default:
                    documentNumber = Guid.NewGuid().ToString();
                    break;
            }
            return documentNumber;
        }

        private static string GetDocumentID(SPListItem document, string structure, string documentType, IEnumerable<ElementoDeConfiguracionDeEstructuraDeRadicado> fields
            , DCDocumentCenterDataContext context)
        {
            string number = structure;
            int dependecyId = Convert.ToInt32(document[CustomSiteColumns.FIELD_DOCSET_DEPARTMNET]);
            DateTime radDate = Convert.ToDateTime(document[SPBuiltInFieldId.Created]);
            //IEnumerable<CampoDisponibleDeConfiguracionDeRadicado> fields = null;
            CampoDisponibleDeConfiguracionDeRadicado consecutiveField = null;


            //fields = context.CamposDeConfiguracionDeEstructuraDeRadicado;


            consecutiveField = (from field in fields
                                where field.Campo.Id.Equals(GlobalConstants.RAD_TYPE_STRUCTURE_FIELD_ID)
                                select field.Campo).SingleOrDefault();

            fields = fields.Where(field => !field.Campo.Id.Equals(GlobalConstants.RAD_TYPE_STRUCTURE_FIELD_ID));



            foreach (ElementoDeConfiguracionDeEstructuraDeRadicado field in fields)
            {
                switch (field.Campo.Etiqueta)
                {
                    case "[AÑO]":
                        ReplaceYearToken(ref number, radDate, field.Campo.Etiqueta);
                        break;
                    case "[MES]":
                        ReplaceMonthToken(ref number, radDate, field.Campo.Etiqueta);
                        break;
                    case "[DIA]":
                        ReplaceDayToken(ref number, radDate, field.Campo.Etiqueta);
                        break;
                    case "[TIPORAD]":
                        ReplaceRadTypeToken(ref number, documentType, field.Campo.Etiqueta, context);
                        break;
                    case "[DEPENDENCIA]":
                        ReplaceDependencyToken(ref number, dependecyId, field.Campo.Etiqueta, document.Web.Url);
                        break;
                }
            }

            number = number.Replace(consecutiveField.Etiqueta, GetMixedConsecutiveID(documentType, document.Web.Site));

            return number;
        }

        private static void ReplaceYearToken(ref string structure, DateTime datetime, string tag)
        {
            structure = structure.Replace(tag, datetime.ToString("yyyy"));
        }

        private static void ReplaceMonthToken(ref string structure, DateTime datetime, string tag)
        {
            structure = structure.Replace(tag, datetime.ToString("MM"));
        }

        private static void ReplaceDayToken(ref string structure, DateTime datetime, string tag)
        {
            structure = structure.Replace(tag, datetime.ToString("dd"));
        }

        private static void ReplaceDependencyToken(ref string structure, int dependecyID, string tag, string siteUrl)
        {
            Seccion entity = QueryRecordsSiteHelper<Seccion>.QueryRecordsEntityById(GlobalConstants.LIST_BIBLIOTECA_SECCION, dependecyID, siteUrl);
            if (entity == default(Seccion)) { throw new Exception(string.Format("No se encuentra la sección con id {0}", dependecyID)); }
            structure = structure.Replace(tag, entity.Código);
        }

        private static void ReplaceRadTypeToken(ref string structure, string radTypeTitle, string tag, DCDocumentCenterDataContext context)
        {
            TipoDeRadicado element = context.TiposDeRadicados.SingleOrDefault(radType => radType.Title.Equals(radTypeTitle));
            if (element == default(TipoDeRadicado)) { throw new Exception(string.Format("El tipo de radicado {0} no se encuentra configurado.", radTypeTitle)); }
            structure = structure.Replace(tag, element.CódigoTipo);
        }

        private static string GetMixedConsecutiveID(string documentType)
        {
            int mixedConsecutiveLenght = GlobalConstants.RAD_TYPE_STRUCTURE_CONSECUTIVE_LENGTH;
            string consecutive = GetNextID(documentType).ToString();
            StringBuilder mixedConsecutive = new StringBuilder();

            int iterations = mixedConsecutiveLenght - mixedConsecutive.Length;

            for (int i = 0; i < iterations; i++) { mixedConsecutive.Append("0"); }
            mixedConsecutive.Append(consecutive);

            return mixedConsecutive.ToString();
        }

        private static string GetMixedConsecutiveID(string documentType, SPSite site)
        {
            int mixedConsecutiveLenght = GlobalConstants.RAD_TYPE_STRUCTURE_CONSECUTIVE_LENGTH;
            string consecutive = GetNextID(documentType, site).ToString();
            StringBuilder mixedConsecutive = new StringBuilder();

            int iterations = mixedConsecutiveLenght - consecutive.Length;

            for (int i = 0; i < iterations; i++) { mixedConsecutive.Append("0"); }
            mixedConsecutive.Append(consecutive);

            return mixedConsecutive.ToString();
        }

        public static bool AlreadyExists(string title, string code)
        {
            bool exists = false;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                exists = ctx.TiposDeRadicados.Count(radType => radType.Title.Equals(title) || radType.CódigoTipo.Equals(code)) > 0;
            }
            return exists;
        }

        public static bool AlreadyExists(string title, string code, int radTypeId)
        {
            bool exists = false;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                exists = ctx.TiposDeRadicados.Count(radType => !radType.Id.Equals(radTypeId) && (radType.Title.Equals(title) || radType.CódigoTipo.Equals(code))) > 0;
            }
            return exists;
        }

        public static IEnumerable<CampoDisponibleDeConfiguracionDeRadicado> GetAllFields()
        {
            IEnumerable<CampoDisponibleDeConfiguracionDeRadicado> elements = null;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                elements = ctx.CamposDeConfiguracionDeEstructuraDeRadicado.OrderBy(field => field.Title);
            }
            return elements;
        }

        public static IEnumerable<CampoDisponibleDeConfiguracionDeRadicado> GetAvailableFields(int radTypeId)
        {
            IEnumerable<CampoDisponibleDeConfiguracionDeRadicado> elements = null;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                TipoDeRadicado entity = ctx.TiposDeRadicados.SingleOrDefault(radType => radType.Id.Equals(radTypeId));

                var existingFields = from configElement in entity.ElementoDeConfiguracionDeEstructuraDeRadicado
                                     select configElement.Campo;


                elements = ctx.CamposDeConfiguracionDeEstructuraDeRadicado.ToList().Except(existingFields.ToList());
            }
            return elements;
        }

        public static IEnumerable<ElementoDeConfiguracionDeEstructuraDeRadicado> GetRadType(int radTypeId)
        {
            IEnumerable<ElementoDeConfiguracionDeEstructuraDeRadicado> elements = null;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                TipoDeRadicado entity = ctx.TiposDeRadicados.SingleOrDefault(radType => radType.Id.Equals(radTypeId));
                if (entity == default(TipoDeRadicado)) { throw new Exception(string.Format("No existe un tipo de radicado con idetificador {0}", radTypeId)); }
                elements = entity.ElementoDeConfiguracionDeEstructuraDeRadicado.OrderBy(config => config.Orden);
            }
            return elements;
        }

        public static IEnumerable<TipoDeRadicado> GetRadTypes()
        {
            IEnumerable<TipoDeRadicado> elements = null;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                elements = ctx.TiposDeRadicados.OrderBy(radType => radType.Title);
            }
            return elements;
        }

        public static void AddRadType(string title, string code, CampoConfiguracionDTO[] configFields)
        {
            DocumentIDServiceClient client = new DocumentIDServiceClient(SPServiceContext.Current);
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                List<ElementoDeConfiguracionDeEstructuraDeRadicado> elements = new List<ElementoDeConfiguracionDeEstructuraDeRadicado>();

                client.AddDocumentIDBank(title, ExecuteOptions.None);
                TipoDeRadicado parent = AddRadType(title, code, ctx);

                foreach (CampoConfiguracionDTO currentConfig in configFields)
                {
                    elements.Add(FromConfigDTOToLinqEntity(currentConfig, parent, ctx));
                }

                AddConfigElements(elements, ctx);
            }

        }

        public static void AddRadType(string title, string code, CampoConfiguracionDTO[] configFields, SPSite site)
        {
            DocumentIDServiceClient client = new DocumentIDServiceClient(SPServiceContext.GetContext(site));
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(site.Url))
            {
                List<ElementoDeConfiguracionDeEstructuraDeRadicado> elements = new List<ElementoDeConfiguracionDeEstructuraDeRadicado>();

                client.AddDocumentIDBank(title, ExecuteOptions.None);
                TipoDeRadicado parent = AddRadType(title, code, ctx);

                foreach (CampoConfiguracionDTO currentConfig in configFields)
                {
                    elements.Add(FromConfigDTOToLinqEntity(currentConfig, parent, ctx));
                }

                AddConfigElements(elements, ctx);
            }

        }

        private static TipoDeRadicado AddRadType(string title, string code, DCDocumentCenterDataContext context)
        {
            TipoDeRadicado element = new TipoDeRadicado { Title = title, BancoDeConsecutivosAsociado = string.Empty, CódigoTipo = code };
            context.TiposDeRadicados.InsertOnSubmit(element);
            context.SubmitChanges();
            return element;
        }

        public static void UpdateRadType(int radTypeId, string code, string title, CampoConfiguracionDTO[] configFields)
        {
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                TipoDeRadicado entity = UpdateRadType(radTypeId, code, title, ctx);
                DeleteConfigElementsOnUpdate(configFields, entity.ElementoDeConfiguracionDeEstructuraDeRadicado, ctx);
                foreach (CampoConfiguracionDTO currentCampoConfiguracionDTO in configFields)
                {
                    UpdateOrCreateConfigElement(currentCampoConfiguracionDTO, entity, entity.ElementoDeConfiguracionDeEstructuraDeRadicado, ctx);
                }
            }
        }

        private static TipoDeRadicado UpdateRadType(int radTypeId, string code, string title, DCDocumentCenterDataContext context)
        {
            TipoDeRadicado entity = context.TiposDeRadicados.SingleOrDefault(radType => radType.Id.Equals(radTypeId));
            if (entity == default(TipoDeRadicado)) { throw new Exception(string.Format("No existe un tipo de documento con identificador {0}", radTypeId)); }
            entity.CódigoTipo = code;
            entity.Title = title;
            context.SubmitChanges();
            return entity;
        }

        private static ElementoDeConfiguracionDeEstructuraDeRadicado FromConfigDTOToLinqEntity(CampoConfiguracionDTO dtoField, TipoDeRadicado radType, DCDocumentCenterDataContext context)
        {
            CampoDisponibleDeConfiguracionDeRadicado field = new CampoDisponibleDeConfiguracionDeRadicado { Title = dtoField.Title, Id = dtoField.FieldId };
            context.CamposDeConfiguracionDeEstructuraDeRadicado.Attach(field);
            ElementoDeConfiguracionDeEstructuraDeRadicado element = new ElementoDeConfiguracionDeEstructuraDeRadicado
            {
                Campo = field,
                TipoDeRadicado = radType,
                Title = String.Format("Elemento de configuracion para el campo {0}, del tipo de radicado {1}", field.Title, radType.Title),
                Orden = dtoField.Order
            };

            return element;
        }

        private static void AddConfigElements(List<ElementoDeConfiguracionDeEstructuraDeRadicado> elements, DCDocumentCenterDataContext context)
        {
            if (elements.Count > 0)
            {
                context.ElementosDeConfiguracionDeEstructuraDeRadicado.InsertAllOnSubmit(elements);
                context.SubmitChanges();
            }
        }

        private static void UpdateOrCreateConfigElement(CampoConfiguracionDTO dtoField, TipoDeRadicado radType, IEnumerable<ElementoDeConfiguracionDeEstructuraDeRadicado> radTypeConfigElements
            , DCDocumentCenterDataContext context)
        {
            ElementoDeConfiguracionDeEstructuraDeRadicado element = radTypeConfigElements.SingleOrDefault(config => config.Id.Equals(dtoField.ConfigItemId));
            if (element == default(ElementoDeConfiguracionDeEstructuraDeRadicado))
            {
                CampoDisponibleDeConfiguracionDeRadicado parentField = new CampoDisponibleDeConfiguracionDeRadicado
                {
                    Id = dtoField.FieldId,
                    Title = dtoField.Title
                };

                context.CamposDeConfiguracionDeEstructuraDeRadicado.Attach(parentField);

                element = new ElementoDeConfiguracionDeEstructuraDeRadicado
                {
                    Campo = parentField,
                    TipoDeRadicado = radType,
                    Orden = dtoField.Order,
                    Title = String.Format("Elemento de configuracion para el campo {0}, del tipo de radicado {1}", dtoField.Title, radType.Title),
                };
                context.ElementosDeConfiguracionDeEstructuraDeRadicado.InsertOnSubmit(element);
            }
            else
            {
                element.Orden = dtoField.Order;
            }
            context.SubmitChanges();
        }

        private static void DeleteConfigElementsOnUpdate(CampoConfiguracionDTO[] configFields, IEnumerable<ElementoDeConfiguracionDeEstructuraDeRadicado> radTypeConfigElements
            , DCDocumentCenterDataContext context)
        {
            IEnumerable<ElementoDeConfiguracionDeEstructuraDeRadicado> deletableElements = null;

            deletableElements = from element in radTypeConfigElements
                                join
                                    dtoElement in configFields on element.Id equals dtoElement.ConfigItemId
                                where !dtoElement.ConfigItemId.Equals(0)
                                select element;

            deletableElements = radTypeConfigElements.Except(deletableElements);

            context.ElementosDeConfiguracionDeEstructuraDeRadicado.RecycleAllOnSubmit(deletableElements);
            context.SubmitChanges();
        }

        public static void DeleteRadType(int radTypeId)
        {
            TipoDeRadicado radType = null;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                radType = ctx.TiposDeRadicados.SingleOrDefault(type => type.Id.Equals(radTypeId));
                if (radType == default(TipoDeRadicado)) { throw new Exception(string.Format("No existe un tipo de radicado que corresponda al id {0}", radTypeId)); }
                DeleteRadType(radType, ctx);
                DeleteRadTypeConfigItems(radType, ctx);
                ctx.SubmitChanges();
            }
            DeleteRadTypeBank(radType.Title);
        }

        public static void DeleteRadType(int radTypeId, SPSite site)
        {
            TipoDeRadicado radType = null;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(site.Url))
            {
                radType = ctx.TiposDeRadicados.SingleOrDefault(type => type.Id.Equals(radTypeId));
                if (radType == default(TipoDeRadicado)) { throw new Exception(string.Format("No existe un tipo de radicado que corresponda al id {0}", radTypeId)); }
                DeleteRadType(radType, ctx);
                DeleteRadTypeConfigItems(radType, ctx);
                ctx.SubmitChanges();
                DeleteRadTypeBank(radType.Title, site);
            }

        }

        private static void DeleteRadType(TipoDeRadicado radType, DCDocumentCenterDataContext context)
        {
            context.TiposDeRadicados.RecycleOnSubmit(radType);
        }

        private static void DeleteRadTypeConfigItems(TipoDeRadicado radType, DCDocumentCenterDataContext context)
        {
            context.ElementosDeConfiguracionDeEstructuraDeRadicado.RecycleAllOnSubmit(radType.ElementoDeConfiguracionDeEstructuraDeRadicado);
        }

        private static void DeleteRadTypeBank(string radTypeName)
        {
            DocumentIDServiceClient client = new DocumentIDServiceClient(SPServiceContext.Current);
            client.DeleteDocumentIDBank(radTypeName, ExecuteOptions.None);
        }

        private static void DeleteRadTypeBank(string radTypeName, SPSite site)
        {
            DocumentIDServiceClient client = new DocumentIDServiceClient(SPServiceContext.GetContext(site));
            client.DeleteDocumentIDBank(radTypeName, ExecuteOptions.None);
        }

        public static void RestartRadTypeBank(int radTypeId)
        {
            TipoDeRadicado radType = null;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(SPContext.Current.Web.Url))
            {
                radType = ctx.TiposDeRadicados.SingleOrDefault(type => type.Id.Equals(radTypeId));
            }
            if (radType == default(TipoDeRadicado)) { throw new Exception(string.Format("No existe un tipo de radicado que corresponda al id {0}", radTypeId)); }
            RestartRadTypeBank(radType.Title);
        }

        public static void RestartRadTypeBank(int radTypeId, SPSite site)
        {
            TipoDeRadicado radType = null;
            using (DCDocumentCenterDataContext ctx = new DCDocumentCenterDataContext(site.Url))
            {
                radType = ctx.TiposDeRadicados.SingleOrDefault(type => type.Id.Equals(radTypeId));
            }
            if (radType == default(TipoDeRadicado)) { throw new Exception(string.Format("No existe un tipo de radicado que corresponda al id {0}", radTypeId)); }
            RestartRadTypeBank(radType.Title, site);
        }

        private static void RestartRadTypeBank(string radTypeName)
        {
            DocumentIDServiceClient client = new DocumentIDServiceClient(SPServiceContext.Current);
            client.ResetDocumentIDBank(radTypeName, ExecuteOptions.None);
        }

        private static void RestartRadTypeBank(string radTypeName, SPSite site)
        {
            DocumentIDServiceClient client = new DocumentIDServiceClient(SPServiceContext.GetContext(site));
            client.ResetDocumentIDBank(radTypeName, ExecuteOptions.None);
        }

    }
}
